#!/usr/bin/env python
#-*- coding:utf-8 -*-

# piemenu.py
#
# Copyright (c) 2007 Marcelo Lira dos Santos
#
# Author: Marcelo Lira dos Santos <setanta@gmail.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation; either version 2 of
# the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA

import pygtk
pygtk.require('2.0')
import gtk
import gobject
import cairo
import math

class PieMenu(gobject.GObject):

    ICON_RADIUS = 16
    ICON_SCALE_UP = 2.0
    ICON_CLICKED_FACTOR = 0.75
    ANIMATION_SPEED = 8
    #ANIMATION_SPEED = 10
    ANIMATION_STEP = 2
    ANIMATION_TURN_STEP = 0.05
    ANIMATION_ZOOM_FACTOR = 0.95

    def __init__(self, pos=(0,0), radius=80, canvas=None):
        gobject.GObject.__init__(self)
        self.canvas = canvas
        self.pos = pos
        self.items = []

        self.item_over = None
        self.item_pressed = None

        self.animating = False
        self.turn = 0.0

        self.visible = False
        self.radius = 0.0
        self.base_radius = radius

        self.icon_scale_up = PieMenu.ICON_SCALE_UP


    def set_pos(self, pos=(0,0)):
        self.pos = pos


    def set_canvas(self, canvas):
        self.canvas = canvas


    def set_radius(self, radius):
        self.radius = radius


    def is_visible(self):
        return self.visible


    def add_item(self, icon_file, callback, *args):
        image = cairo.ImageSurface.create_from_png(icon_file)
        self.items.append((image, callback, args))


    def hide(self):
        if self.is_visible():
            self.animating = True
            gobject.timeout_add(PieMenu.ANIMATION_SPEED, self._animate_hide)

    def _animate_hide(self):
        self.radius -= PieMenu.ANIMATION_STEP
        self.turn -= PieMenu.ANIMATION_TURN_STEP

        if self.radius <= 0:
            self.radius = 0
            self.visible = False
            self.animating = False

        self.canvas.refresh()

        return self.animating


    def show(self):
        if not self.is_visible():
            self.animating = True
            gobject.timeout_add(PieMenu.ANIMATION_SPEED, self._animate_show)

    def _animate_show(self):
        self.radius += PieMenu.ANIMATION_STEP
        self.turn += PieMenu.ANIMATION_TURN_STEP

        if self.radius >= self.base_radius:
            self.radius = self.base_radius

            self.icon_scale_up = PieMenu.ICON_SCALE_UP

            self.visible = True
            self.animating = False

        self.canvas.refresh()

        return self.animating


    def get_item_by_position(self, pos):
        base_x, base_y = self.pos

        n = len(self.items)
        for i in xrange(n):
            x = base_x + self.radius * math.cos(i * math.pi / (n / 2.0) + self.turn)
            y = base_y + self.radius * math.sin(i * math.pi / (n / 2.0) + self.turn)

            dist = math.hypot(abs(x - pos[0]), abs(y - pos[1]))

            radius = PieMenu.ICON_RADIUS
            if self.items[i] == self.item_over:
                radius *= self.icon_scale_up

            if dist < radius:
                return self.items[i]

        return None


    def button_press(self):
        if self.item_over:
            self.item_pressed = self.item_over

            if self.icon_scale_up == PieMenu.ICON_SCALE_UP:
                self.icon_scale_up *= PieMenu.ICON_CLICKED_FACTOR
                self.canvas.refresh()


    def button_release(self, pos):
        self.icon_scale_up = PieMenu.ICON_SCALE_UP

        if self.item_over:
            self.canvas.refresh()

            if self.item_pressed == self.item_over:
                self.item_over[1](self.item_over[2])

        self.item_pressed = None


    def is_mouse_over(self, pos):
        base_x, base_y = self.pos

        n = len(self.items)
        for i in xrange(n):
            x = base_x + self.radius * math.cos(i * math.pi / (n / 2.0) + self.turn)
            y = base_y + self.radius * math.sin(i * math.pi / (n / 2.0) + self.turn)

            dist = math.hypot(abs(x - pos[0]), abs(y - pos[1]))

            radius = PieMenu.ICON_RADIUS
            if self.items[i] == self.item_over:
                radius *= self.icon_scale_up

            if dist < radius:
                self.item_over = self.items[i]
                self.canvas.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
                return True

        if self.item_over:
            self.item_over = None
            self.canvas.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.TOP_LEFT_ARROW))
            return True

        return False


    def draw(self, context):
        if not self.radius:
            return

        icon_scale_down = 1.0 / self.icon_scale_up
        base_x, base_y = self.pos
        r, g, b = 150, 0, 0

        context.clip()

        n = len(self.items)
        resized = False
        for i in xrange(n):
            x = base_x + self.radius * math.cos(i * math.pi / (n / 2.0) + self.turn)
            y = base_y + self.radius * math.sin(i * math.pi / (n / 2.0) + self.turn)

            icon = self.items[i][0]

            if resized:
                resized = False
                context.scale(icon_scale_down, icon_scale_down)

            if self.items[i] == self.item_over:
                context.scale(self.icon_scale_up, self.icon_scale_up)
                x = x * icon_scale_down
                y = y * icon_scale_down
                resized = True

            w = icon.get_width() / 2
            h = icon.get_height() / 2
            r = w * 1.0

            context.set_source_rgba(r, g, b, 0.2)
            context.arc(x, y, r, 0, 2 * math.pi)
            context.fill()

            context.set_source_surface(icon, x - w, y - h)
            context.paint()

            if resized:
                resized = False
                context.scale(icon_scale_down, icon_scale_down)

        context.stroke()

